/************************************************************************

								Heroes IV
					  Copyright 2000, The 3DO Company

	------------------------------------------------------------------
  						       creature_array_window.cpp

	$Header: /game/creature_array_window.cpp $

	$NoKeywords: $

 ************************************************************************/

// kill the 255+ character identifier warning.  Templates tend to exceed that easily.
#include "precompiled.h"
#pragma warning( disable: 4786 )

#include "creature_array_window.h"

#include <list>
#include "abbreviate_number.h"
#include "adaptor_handler.h"
#include "bitmap_group.h"
#include "bitmap_group_cache.h"
#include "bitmap_layer_cache_window.h"
#include "bound_handler.h"
#include "button.h"
#include "creature.h"
#include "creature_traits.h"
#include "creature_type.h"
#include "drag_artifact.h"
#include "drag_creature.h"
#include "external_string.h"
#include "format_string.h"
#include "hero.h"
#include "mouse_event.h"
#include "replace_keywords.h"
#include "simple_dialog.h"
#include "split_creatures_dialog.h"
#include "standard_fonts.h"
#include "text_window.h"

t_bitmap_group_cache g_divide_cursor( "cursor.split" );
t_bitmap_group_cache g_creature_rings( "creature_rings" );

// ---------------------------------------------------------------------------
// button for creature array
// ---------------------------------------------------------------------------
namespace
{
	class t_drop_target : public t_window
	{
	public:
		t_drop_target( t_screen_rect const& rect, t_creature_array_window* parent,
		               int slot );

		virtual bool accept_drag( t_drag_object* drag, t_mouse_event const& event);
		virtual void drag_event( t_mouse_event const& event );
	    virtual void left_button_down( t_mouse_event const& event );
		virtual void left_button_up( t_mouse_event const& event );
		virtual void left_double_click( t_mouse_event const& event );
		virtual void mouse_move( t_mouse_event const& event );
	    virtual void mouse_leaving( t_window* old_window, t_window* new_window,
		                            t_mouse_event const& event );
 	protected:
		void select();

		t_creature_array_window* m_array_window;
		int                      m_slot;
	};


	typedef std::list< t_creature_array_window * > t_creature_array_window_list;
}; // unnamed namespace

// ---------------------------------------------------------------------------
// button for creature array
// ---------------------------------------------------------------------------
t_drop_target::t_drop_target( t_screen_rect const& rect, t_creature_array_window* parent,
							 int slot ) : t_window( rect, k_some_transparency, parent )
{
	assert( parent );
	m_array_window = parent;
	m_slot = slot;
}

// ---------------------------------------------------------------------------
// button for creature array
// ---------------------------------------------------------------------------
bool t_drop_target::accept_drag( t_drag_object* drag, t_mouse_event const& event)
{
	t_counted_ptr<t_drag_creature> drag_creature = dynamic_cast<t_drag_creature*>(drag);

	if (drag_creature != 0)
	{
		if ( m_array_window->is_valid_drag_drop( drag_creature, m_slot ) )
		{
			m_array_window->accept_creature_drag( drag_creature, m_slot );
			return true;
		}
		else
		{
			return false;
		}
	}

	t_counted_ptr<t_drag_artifact> drag_artifact = dynamic_cast<t_drag_artifact*>(drag);
	
	if (drag_artifact != 0)
	{
		if ( m_array_window->is_valid_drag_drop( drag_artifact, m_slot ) )
		{
			m_array_window->accept_artifact_drag( drag_artifact, m_slot );
			return true;
		}
		else
		{
			return false;
		}
	}
	return t_window::accept_drag( drag, event );
}

// ---------------------------------------------------------------------------
// button for creature array
// ---------------------------------------------------------------------------
void t_drop_target::left_button_down( t_mouse_event const& event )
{
	select();
}

// ---------------------------------------------------------------------------
// button for creature array
// ---------------------------------------------------------------------------
void t_drop_target::left_button_up( t_mouse_event const& event )
{
}

// ---------------------------------------------------------------------------
// button for creature array
// ---------------------------------------------------------------------------
void t_drop_target::left_double_click( t_mouse_event const& event )
{
	if (dynamic_cast<t_drag_creature*>(get_drag_object()) != 0)
		return;

	t_creature_array const* army = m_array_window->get_army();

	if (army == 0 || (*army)[m_slot].get_number() == 0)
		return;
	m_array_window->double_click( m_slot );
}

// ---------------------------------------------------------------------------
// button for creature array
// ---------------------------------------------------------------------------
void t_drop_target::select()
{
	if (dynamic_cast<t_drag_creature*>(get_drag_object()) != 0)
		return;

	t_creature_array const* army = m_array_window->get_army();

	if (army == 0 || (*army)[m_slot].get_number() == 0)
		return;
	m_array_window->select_creature( m_slot );
}


// ---------------------------------------------------------------------------
// button for creature array
// ---------------------------------------------------------------------------
void t_drop_target::mouse_move( t_mouse_event const& event )
{
	t_drag_creature * drag_creature = dynamic_cast<t_drag_creature*>(get_drag_object());

	if ( drag_creature != 0 && m_array_window->is_valid_drag_drop( drag_creature, m_slot ) )
		m_array_window->set_highlight( m_slot, true );
}

// ---------------------------------------------------------------------------
// button for creature array
// ---------------------------------------------------------------------------
void t_drop_target::mouse_leaving( t_window* old_window, t_window* new_window,
		                           t_mouse_event const& event )
{
	t_drag_creature * drag_creature = dynamic_cast<t_drag_creature*>(get_drag_object());

	if ( drag_creature != 0 && m_array_window->is_valid_drag_drop( drag_creature, m_slot ) )
		m_array_window->set_highlight( m_slot, false );
}

// ---------------------------------------------------------------------------
// button for creature array
// ---------------------------------------------------------------------------
void t_drop_target::drag_event( t_mouse_event const& event )
{
	if ( !m_array_window->get_allow_drags() )
		return;
	
	t_creature_array* army = m_array_window->get_army();

	if (army == 0)
		return;

	t_creature_stack& source_stack = (*army)[m_slot];

	if (source_stack.get_number() == 0)
		return;

	bool             use_split = (event.shift_key || m_array_window->in_divide_mode())
								 && source_stack.get_number() > 1;


	t_creature_stack_ptr drag_stack;

	if (use_split)
	{
		drag_stack = new t_creature( source_stack.get_creature()->get_creature_type(),
			                         source_stack.get_number() );
		drag_stack->copy_bonuses( source_stack );
		drag_stack->set_raw_adventure_movement( source_stack.get_raw_adventure_movement_left() );
	}
	else
	{
		drag_stack = new t_creature_stack;
		army->swap( drag_stack, m_slot );
		m_array_window->update( true );
		m_array_window->changed();
	}

	t_drag_creature* drag = new t_drag_creature( drag_stack, m_array_window, m_slot,
		                                         m_array_window->get_transfer_charge( m_slot ),
												 use_split );

	m_array_window->cancel_divide_mode();
	begin_drag( drag );
}


// ---------------------------------------------------------------------------
// All the windows that are selection-linked together 
// (That is: selecting any one circle in any of the windows
// deselects all the other circles in all the other windows)
// ---------------------------------------------------------------------------
class t_creature_array_window::t_linked_windows : public t_counted_object, public t_uncopyable
{
	public:
		t_linked_windows( t_creature_array_window * initial_window );
		void merge_window_into_list( t_creature_array_window * new_window );
		void on_destroy_window( t_creature_array_window * window );
		void unselect_all_linked( t_creature_array_window * requestor );

	private:
		t_creature_array_window_list  m_list;
};

// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
t_creature_array_window::t_linked_windows::t_linked_windows( t_creature_array_window * initial_window )
{
	m_list.push_back( initial_window );
}

// ---------------------------------------------------------------------------
// Add a window to the highlight list. Also links all windows linked to the 
// new_window onto this list
// ---------------------------------------------------------------------------
void t_creature_array_window::t_linked_windows::merge_window_into_list( t_creature_array_window * new_window )
{
	// Move everything from other window's link to this list
	t_creature_array_window::t_linked_windows_ptr source_link = new_window->m_linked_windows;

	t_creature_array_window_list::iterator source_iter = source_link->m_list.begin();

	while ( source_iter != source_link->m_list.end() )
	{
		assert( (*source_iter)->m_linked_windows == source_link );

		(*source_iter)->m_linked_windows = this;

		m_list.push_back( *source_iter );

		source_iter = source_link->m_list.erase( source_iter );
	}
}

// ---------------------------------------------------------------------------
// The given window is going away, remove from list
// ---------------------------------------------------------------------------
void t_creature_array_window::t_linked_windows::on_destroy_window( t_creature_array_window * window )
{
	t_creature_array_window_list::iterator iter;
		
	for ( iter = m_list.begin(); iter != m_list.end(); iter++ )
	{
		assert( (*iter)->m_linked_windows.get() == this );
		if ( *iter == window )
		{
			m_list.erase( iter );
			return;
		}
	}

	assert( false );
}

// ---------------------------------------------------------------------------
// The point of this class: unselect every window in the list (except 
// for the window that is selecting a new creature)
// ---------------------------------------------------------------------------
void t_creature_array_window::t_linked_windows::unselect_all_linked( t_creature_array_window * requestor )
{
	t_creature_array_window_list::iterator iter;
		
	for ( iter = m_list.begin(); iter != m_list.end(); iter++ )
	{
		assert( (*iter)->m_linked_windows.get() == this );
		if ( *iter != requestor )
		{
			(*iter)->select_creature( -1 );
		}	
	}
}


// ---------------------------------------------------------------------------
// window for displaying and manipulating lists of creatures
// ---------------------------------------------------------------------------
t_creature_array_window::t_creature_array_window( t_screen_point const& origin, 
		                                          t_creature_array* army,
												  t_layout layout, int transfer_cost,
												  t_window* parent, bool accept_foreign_windows,
												  bool allow_drags )
					   : t_window( t_screen_rect(origin.x, origin.y, origin.x, origin.y),
					               k_completely_transparent, parent ), m_army( army )
{
	m_rings = g_creature_rings.get();
	m_in_divide_mode = false;
	m_selected_slot = -1;
	m_base_transfer_cost = transfer_cost;
    m_accept_foreign_windows = accept_foreign_windows;
	m_allow_drags = allow_drags;
    m_sticky = false;

	// Create linkage data
	m_linked_windows = new t_linked_windows( this );

	// create portraits / buttons
	int            i;
	t_screen_point point;

	static char const* k_names[3][3] = 
	{
		{ "top_left",		"top",		"top_right" },
		{ "left",			"middle",	"right" },
		{ "bottom_left",	"bottom",	"bottom_right" }
	};

	static int const k_layout[4][k_size][2] =
	{
		{{ 1, 0 }, { 1, 1 }, { 1, 1 }, { 1, 1 }, { 1, 1 }, { 1, 1 }, { 1, 2 }},
		{{ 0, 0 }, { 0, 1 }, { 0, 1 }, { 0, 1 }, { 0, 1 }, { 0, 1 }, { 0, 2 }},
		{{ 2, 0 }, { 2, 1 }, { 2, 1 }, { 2, 1 }, { 2, 1 }, { 2, 1 }, { 2, 2 }},
		{{ 0, 0 }, { 2, 0 }, { 0, 1 }, { 2, 1 }, { 0, 1 }, { 2, 2 }, { 1, 2 }}
	};

	t_bitmap_layer const* layer[3][3];
	t_bitmap_layer const* highlight[3][3];
	t_bitmap_layer const* disabled[3][3];
	int                   row;
	int                   column;
	std::string           layer_name;

	// load layers from border file
	for (row = 0; row < 3; row++)
	{
		for (column = 0; column < 3; column++)
		{
			layer_name = k_names[row][column];
			layer[row][column] = m_rings->find( layer_name );
			layer_name += "_highlight" ;
			highlight[row][column] = m_rings->find( layer_name );
			
			layer_name = k_names[row][column];
			layer_name += "_disabled" ;
			disabled[row][column] = m_rings->find( layer_name );
		}
	}

	// find the offset of the portrait relative to the border
	t_screen_rect         portrait_rect = m_rings->find( "portrait" )->get_rect();
	t_screen_point        portrait_offset = portrait_rect.top_left();
	t_screen_point        border_offset;
	t_bitmap_layer const* border;
	t_bitmap_layer const* inset = m_rings->find( "inset" );
	t_bitmap_layer const* inset_text = m_rings->find( "inset_text" );
	t_bitmap_layer const* mana_bar = m_rings->find( "mana_bar" );
	t_bitmap_layer const* mana_border = m_rings->find( "mana_bar_border" );
	t_bitmap_layer const* health_bar = m_rings->find( "health_bar" );
	t_bitmap_layer const* health_border = m_rings->find( "health_bar_border" );
	t_screen_point        inset_offset = inset->get_rect().top_left();
	t_screen_rect         text_rect = inset_text->get_rect() - inset_offset;
	t_cached_font         font = get_font( text_rect.height() );

	point.x = 0;
	point.y = 0;
	for (i = 0; i < k_size; i++)
	{
		t_creature_windows& slot = m_slot[i];

		row = k_layout[layout][i][0];
		column = k_layout[layout][i][1];
		// get the appropriate border
		border = layer[row][column];
		// make upper left corner of border == point
		border_offset = -border->get_rect().top_left();
		// create a transparent "drop target" behind button to shunt messages to button
		// when it's invisible.
		t_window* drop_target;
		
		drop_target = new t_drop_target( point + portrait_rect + border_offset, this, i );
		// create button
		slot.portrait_window = new t_bitmap_layer_cache_window( t_bitmap_layer_ptr(),
			                       t_screen_point(0,0), drop_target );

		// create border
		slot.border = new t_bitmap_layer_window( border, point + border_offset, this );
		// create highlighted border
		slot.selected = new t_bitmap_layer_window( highlight[row][column], 
			                                       point + border_offset, this );
		// hide highlighted border
		slot.selected->set_visible( false );
		slot.disabled = new t_bitmap_layer_window( disabled[row][column], 
			                                       point + border_offset, this );
		slot.disabled->set_visible( false );

		// create inset border
		slot.inset = new t_bitmap_layer_window( inset, point + border_offset, this );
		slot.inset->set_visible( false );
		// create text window
 		slot.text = new t_text_window( font, text_rect, slot.inset, "", t_pixel_24( 0,0,0 ) );
		slot.text->set_center_horizontal();
		slot.text->set_drop_shadow( true, t_pixel_24(200,200,200) );
		// create health bar
		slot.mana_bar.create( mana_bar, mana_border, -inset_offset, slot.inset );
		slot.health_bar.create( health_bar, health_border, -inset_offset, slot.inset );

		// initialize this item as not transferred.
		slot.transfer_charge = 0;
		if (layout != k_stacked_rows)
			point.x += border->get_width();
		else
		{
			if (row == 0)
				point.y += border->get_rect().height();
			else
			{
				point.x += m_slot[i-1].border->get_width();
				point.y -= layer[0][0]->get_rect().height();
			}
		}
	}

	// move highlights to front
	for (i = 0; i < k_size; i++)
	{
		t_creature_windows& slot = m_slot[i];

//		slot.selected->move_to_front();
		slot.inset->move_to_front();
	}
	set_size_from_children();
	update();
}

// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
t_creature_array_window::~t_creature_array_window()
{
	if ( m_linked_windows.get() )
		m_linked_windows->on_destroy_window( this );
}


// ---------------------------------------------------------------------------
// Make sure any drag we started ends before we go away
// ---------------------------------------------------------------------------
void t_creature_array_window::on_close()
{
	// Note this must be done no later than this, but this may already
	// be too late because of dependencies on other peer windows or
	// resources managed by the parent.  In that case, call cancel_drag 
	// somewhere earlier in the parent's shutdown chain
	cancel_drag();
}


// ---------------------------------------------------------------------------
// window for displaying and manipulating lists of creatures
// ---------------------------------------------------------------------------
int t_creature_array_window::get_frame_height() const
{
	return m_slot[0].border->get_height();
}

// ---------------------------------------------------------------------------
// window for displaying and manipulating lists of creatures
// ---------------------------------------------------------------------------
extern t_external_string const k_text_spell_points;
extern t_external_string const k_text_hit_points;

void t_creature_array_window::update( bool keep_existing_display )
{
	int         i;
	std::string text;

	if (m_army == 0)
	{
		for (i = 0; i < k_size; i++)
		{
			t_creature_windows& slot = m_slot[i];

			slot.portrait_window->set_bitmap( t_bitmap_layer_ptr() );
			slot.inset->set_visible( false );
			slot.portrait_window->set_help_balloon_text( "" );
			slot.portrait_window->set_right_click_text( "" );
		}
		set_cursor( get_cursor( false ) );
		select_creature( -1 );
		return;
	}

	for (i = 0; i < k_size; i++)
	{
		t_creature_windows&     slot = m_slot[i];
		t_creature_stack const& creature = (*m_army)[i];

		std::string right_click_text = creature.get_right_click_text();

		slot.portrait_window->set_bitmap( creature.get_portrait() );

		if (creature.get_number() == 0)
		{
			slot.inset->set_visible( false );
			slot.portrait_window->set_help_balloon_text( "" );
			slot.portrait_window->set_right_click_text( right_click_text );
		}
		else
		{
			slot.inset->set_visible( true );
			if (creature.get_creature() == 0 )
			{
				int health = creature.get_hit_points() - creature.get_wounds();

				text = creature.get_name();
				text += format_string( "\n%i/%i %s\n%i/%i %s",
					    health, creature.get_hit_points(), k_text_hit_points.c_str(),
						creature.get_spell_points(), creature.get_maximum_spell_points(),
						k_text_spell_points.c_str() );
				slot.mana_bar.set_visible( true );
				slot.health_bar.set_visible( true );
				slot.health_bar.set( creature.get_hit_points() - creature.get_wounds(),
					                 creature.get_hit_points() );
				slot.mana_bar.set( creature.get_spell_points(),
					               creature.get_maximum_spell_points() );
				slot.text->set_visible( false );
			}
			else
			{
				text = format_string( "%i ", creature.get_number() ) + creature.get_name();
				slot.mana_bar.set_visible( false );
				slot.health_bar.set_visible( false );
				slot.text->set_visible( true );
				slot.text->set_text( abbreviate_number( creature.get_number(),
									 *slot.text->get_font(), slot.text->get_width()) );
			}
			slot.portrait_window->set_help_balloon_text( text );
			slot.portrait_window->set_right_click_text( right_click_text );
			slot.inset->set_help_balloon_text( text );
			slot.inset->set_right_click_text( right_click_text );
		}
	}

	if (m_in_divide_mode)
	{
		// if divide cursor is not loaded, load it.
		if (m_divide_cursor == 0)
		{
			m_divide_image = g_divide_cursor.get();

			t_bitmap_layer const* image = m_divide_image->begin()->get();
			t_screen_rect         image_size = image->get_rect();
			t_screen_point        center( image_size.right / 2, image_size.bottom / 2 );
			
			m_divide_cursor = new t_mouse_window( center );
			m_divide_cursor->set_visible( false );
			new t_bitmap_layer_window( image, t_screen_point(0,0), m_divide_cursor );
		}
		// set cursor of any slot with 2+ creatures to the divide cursor
		for (i = 0; i < k_size; i++)
		{
			t_creature_windows& slot = m_slot[i];

			if ((*m_army)[i].get_number() > 1)
				slot.portrait_window->set_cursor( m_divide_cursor );
		}
	}
	else
	{
		// reset cursor of all child windows.
		set_cursor( get_cursor( false ) );
	}


	// Update everybody if the currently selected stack has become empty
	// EXCEPT if we're doing a drag and drop in which case we want to leave
	// the data from the "old" selection displayed while the drag is going on.
	if ( !keep_existing_display ) {
		if (m_selected_slot >= 0 && (*m_army)[m_selected_slot].get_number() == 0)
			select_creature( -1 );
	}
}

// ---------------------------------------------------------------------------
// window for displaying and manipulating lists of creatures
// ---------------------------------------------------------------------------
void t_creature_array_window::cancel_divide_mode()
{
	if (!m_in_divide_mode)
		return;
	m_in_divide_mode = false;
	m_cancel_divide_func();
	update();
}

// ---------------------------------------------------------------------------
// window for displaying and manipulating lists of creatures
// ---------------------------------------------------------------------------
void t_creature_array_window::set_divide_mode( t_handler const& cancel_func )
{
	if (m_in_divide_mode)
		return;

	m_in_divide_mode = true;
	m_cancel_divide_func = cancel_func;
	update();
}

// ---------------------------------------------------------------------------
// window for displaying and manipulating lists of creatures
// ---------------------------------------------------------------------------
void t_creature_array_window::set_highlight( int index, bool highlight )
{
	t_creature_windows& slot = m_slot[index];

	slot.border->set_visible( !highlight );
	slot.selected->set_visible( highlight );
}

void t_creature_array_window::set_disabled( int index, bool disable )
{
	t_creature_windows& slot = m_slot[index];

	slot.border->set_visible( disable );
	slot.selected->set_visible( !disable );
	slot.disabled->set_visible( disable );
}

void t_creature_array_window::toggle_highlight( int index )
{
    if ((index < 0) || (index > k_size))
        return;

	t_creature_windows& slot = m_slot[index];

	if (slot.disabled->is_visible())
        return;

    bool highlight = (slot.selected->is_visible());
	slot.border->set_visible( highlight );
	slot.selected->set_visible( !highlight );
}

bool t_creature_array_window::is_slot_highlighted( int slot_number )
{
    if ((slot_number < 0) || (slot_number > k_size))
        return false;

	t_creature_windows& slot = m_slot[slot_number];
    return (slot.selected->is_visible());
}

// ---------------------------------------------------------------------------
// window for displaying and manipulating lists of creatures
// ---------------------------------------------------------------------------
void t_creature_array_window::set_army( t_creature_array* army )
{
	if (army == m_army)
		return;
	m_army = army;
	select_first();
	update();
}

// ---------------------------------------------------------------------------
// window for displaying and manipulating lists of creatures
// ---------------------------------------------------------------------------
void t_creature_array_window::select_first()
{
	int i;

	if (m_army == 0)
		return;

	for (i = 0; i < k_size; i++)
	{
		if ((*m_army)[i].get_number() > 0)
			break;
	}
	if (i == k_size)
		i = -1;
	select_creature( i );
}

void t_creature_array_window::select_leader()
{
	int i;

	if (m_army == 0)
		return;

	if (m_army->empty())
		i = -1;
	else
		i = m_army->get_leader_index();
	select_creature( i );
}


// ---------------------------------------------------------------------------
// window for displaying and manipulating lists of creatures
// ---------------------------------------------------------------------------
void t_creature_array_window::select_first_valid( int start_ndx, bool backwards )
{
	int i;
	int ndx;

	if (m_army == 0)
		return;

	for (i = 0; i < k_size; i++)
	{
		if( backwards )
		{
			ndx = ( start_ndx - i ) % k_size;
			if( ndx < 0 )
				ndx += k_size;
		} else
			ndx = ( start_ndx + i ) % k_size;
		if ((*m_army)[ndx].get_number() > 0)
			break;
	}
	if (i == k_size)
		ndx = -1;
	select_creature( ndx );
}

// ---------------------------------------------------------------------------
// window for displaying and manipulating lists of creatures
// ---------------------------------------------------------------------------
bool t_creature_array_window::select_next()
{
	int i;
	int ndx = 0;
	bool wrapped = false;

	if (m_army == 0)
		return false;

	if( m_selected_slot < 0 )
		m_selected_slot = 0;

	for (i = 1; i < k_size; i++)
	{
		ndx = ( m_selected_slot + i ) % k_size;
		if( ( m_selected_slot + i ) >= k_size )
			wrapped = true;

		if ((*m_army)[ndx].get_number() > 0)
			break;
	}

	if( ndx != m_selected_slot && i != k_size )
		select_creature( ndx );

	return ( wrapped || i == k_size );
}

// ---------------------------------------------------------------------------
// window for displaying and manipulating lists of creatures
// ---------------------------------------------------------------------------
bool t_creature_array_window::select_prev()
{
	int i;
	int ndx = 0;
	bool wrapped = false;

	if (m_army == 0)
		return false;

	if( m_selected_slot < 0 )
		m_selected_slot = 0;

	for (i = 1; i < k_size; i++)
	{
		ndx = ( m_selected_slot - i ) % k_size;
		if( ndx < 0 )
			ndx += k_size;
		if( ( m_selected_slot - i ) < 0 )
			wrapped = true;

		if ((*m_army)[ndx].get_number() > 0)
			break;
	}

	if( ndx != m_selected_slot && i != k_size )
		select_creature( ndx );

	return ( wrapped || i == k_size );
}

// ---------------------------------------------------------------------------
// window for displaying and manipulating lists of creatures
// ---------------------------------------------------------------------------
void t_creature_array_window::select_creature( int index )
{
    if (m_sticky)
    {
	    toggle_highlight( index );
    }
    else
    {
	    int i;

	    if ( index >= 0 )
		    m_linked_windows->unselect_all_linked( this );

	    for (i = 0; i < k_size; i++)
		    set_highlight( i, i == index );
    }
	m_selected_slot = index;
	m_select_handler( this, index );
}

// ---------------------------------------------------------------------------
// window for displaying and manipulating lists of creatures
// ---------------------------------------------------------------------------
void t_creature_array_window::double_click( int index )
{
	m_double_click_handler( this, index );
}


// ---------------------------------------------------------------------------
// creature array window
// check if stack has enough movement for an operation.
// ---------------------------------------------------------------------------
static t_external_string k_singular_insufficient_movement( "needs_movement.does.dialog" );
static t_external_string k_plural_insufficient_movement( "needs_movement.do.dialog" );

bool t_creature_array_window::check_transfer_cost( t_creature_stack const& stack,
												   int slot_number, int cost ) 
{
	if (stack.is_dead())
		return true;
	if (stack.get_raw_adventure_movement_left() < cost)
	{
		std::string text;
		std::string name;

		if (stack.get_number() == 1)
			text = k_singular_insufficient_movement.get();
		else
			text = k_plural_insufficient_movement.get();
		name = stack.get_name( true );
		text = replace_keywords( text, "%the_creature", name, "%the_creatures", name );
		show_popup_text( text, m_slot[slot_number].portrait_window );
		set_highlight( slot_number, false );
		return false;
	}
	return true;
}

// --------------------------------------------------------
// creature array window
// add creatures to a slot
// --------------------------------------------------------
void t_creature_array_window::add( t_creature_stack_ptr stack, int slot, int cost,
								   int transfer_charge )
{
	// check if this is a simple move, or a merge.
	t_creature_stack const& dest_stack = (*m_army)[slot];

	// expend movement 
	stack->change_raw_adventure_movement( -cost );
	if (dest_stack.get_number() == 0)
	{   // simple move.  Assign the appropriate value.
		// if cost is zero, we're within the same army.
		if (cost < 0)
			m_slot[slot].transfer_charge = 0;
		else if (cost == 0)
			m_slot[slot].transfer_charge = transfer_charge;
		else
			m_slot[slot].transfer_charge = cost;
	}
	else
	{   // merging armies
		int old_charge = m_slot[slot].transfer_charge;

		if (cost < 0)
			m_slot[slot].transfer_charge = 0;
		else if (cost == 0)
		{
			// if cost is zero, we are within the same army
			if (transfer_charge < old_charge )
				m_slot[slot].transfer_charge = transfer_charge;
		}
		else
		{	
			// compute how much movement destination stack would have if it
			// were returned to the other army, and subtract the new movement setting.
			int move_lost = dest_stack.get_raw_adventure_movement() + old_charge
						  - stack->get_raw_adventure_movement_left();
			if (move_lost < 0)
				move_lost = 0;
			if (move_lost > cost)
				move_lost = cost;
			m_slot[slot].transfer_charge = move_lost;
		}
	}
	// merge them.
	m_army->add( stack, slot );
	m_change_handler();
}

// --------------------------------------------------------
// creature array window
// split creatures
// --------------------------------------------------------
bool t_creature_array_window::split_dialog( t_drag_creature* drag_creature, int slot_number,
										    int cost_in, int cost_out )
{
	t_creature_array_window* source = drag_creature->get_source_window();
	int                      source_slot = drag_creature->get_slot();
	t_creature const&		 source_stack = *drag_creature->get_data()->get_creature();
	t_creature_stack const&	 dest_stack   = (*m_army)[slot_number];
	t_creature_type          creature_type = source_stack.get_creature_type();
	t_counted_ptr<t_split_creatures_dialog> dialog;

	cancel_drag();

	int min_left = 1;
	int min_right = 1;
	t_split_min_max_data data;
	data.window = this;
	data.left_army = source->get_army();
	data.left_slot = drag_creature->get_slot();
	data.right_army = m_army;
	data.right_slot = slot_number;
	data.min_left = &min_left;
	data.min_right = &min_right;
	m_split_min_max_handler( data );

	dialog = new t_split_creatures_dialog( creature_type, source_stack.get_number(),
										   dest_stack.get_number(), 0, min_left, min_right );
	dialog->set_cursor( get_cursor( false ));

	if (!dialog->run_modal())
		return false;

	int amount = dialog->get_amount() - dest_stack.get_number();

	if (amount == 0)
		return false;

	if ( amount > 0 )
	{
		if (!check_transfer_cost( source_stack, slot_number, cost_in ))
			return false;

		t_creature_stack_ptr transfer = new t_creature( creature_type, amount );

		transfer->set_raw_adventure_movement( source_stack.get_raw_adventure_movement_left() );
		transfer->copy_bonuses( source_stack );
		add( transfer, slot_number, cost_in, drag_creature->get_transfer_charge() );
		source->m_army->remove( amount, source_slot );
	}
	else
	{
		if (!check_transfer_cost( dest_stack, slot_number, cost_out ))
			return false;

		t_creature_stack_ptr transfer = new t_creature( creature_type, -amount );

		transfer->set_raw_adventure_movement( dest_stack.get_raw_adventure_movement_left() );
		transfer->copy_bonuses( source_stack );
		source->add( transfer, slot_number, cost_out, m_slot[slot_number].transfer_charge );
		m_army->remove( -amount, slot_number );
	}
	source->update();

	return true;
}

// ---------------------------------------------------------------------------
// creature array window
// clear a slot in the army
// ---------------------------------------------------------------------------
void t_creature_array_window::clear_slot( int index )
{
	if (m_army != 0)
		m_army->clear( index );
}

// ---------------------------------------------------------------------------
// creature array window
// Is this slot a valid target for this drag?
// ---------------------------------------------------------------------------
bool t_creature_array_window::is_valid_drag_drop( t_drag_artifact * drag_artifact, int slot_number )
{
	t_creature_stack& dest_stack = (*m_army)[slot_number];

	if ( dest_stack.get_number() == 0 )
		return false;

	// Check with external validation function
	bool accept = true;
	t_drag_drop_validate_data data;
	data.drag_target = this;
	data.drag_target_slot = slot_number;
	data.drag_source = drag_artifact;

	m_drag_validate_handler( data, accept );

	return accept;
}

// ---------------------------------------------------------------------------
// creature array window
// handle a drag onto a creature slot
// ---------------------------------------------------------------------------
void t_creature_array_window::accept_artifact_drag( t_drag_artifact* drag, int slot_number )
{
	// Should be checked already
	assert( is_valid_drag_drop( drag, slot_number ) );

	t_artifact        artifact = drag->get_artifact();
	t_creature_stack& dest_stack = (*m_army)[slot_number];

	dest_stack.add( artifact );
	drag->accepted( this );
	m_change_handler();
}

// ---------------------------------------------------------------------------
// creature array window
// Is this slot a valid target for this drag?
// ---------------------------------------------------------------------------
bool t_creature_array_window::is_valid_drag_drop( t_drag_creature * drag_creature, int slot_number )
{
	t_creature_array_window* source = drag_creature->get_source_window();
	int                      source_slot = drag_creature->get_slot();
	t_creature_stack_ptr     source_stack = drag_creature->get_data();


    // Allow foreign window drags?
	if (source != this && !m_accept_foreign_windows)
        return false;

	// Check with external validation function
	bool accept = true;
	t_drag_drop_validate_data data;
	data.drag_target = this;
	data.drag_target_slot = slot_number;
	data.drag_source = drag_creature;

	m_drag_validate_handler( data, accept );

	return accept;
}

// ---------------------------------------------------------------------------
// creature array window
// handle a drag onto a creature slot
// ---------------------------------------------------------------------------
void t_creature_array_window::accept_creature_drag( t_drag_creature* drag_creature, 
												    int slot_number )
{
	assert( is_valid_drag_drop( drag_creature, slot_number ) );

	t_creature_array_window*	source			= drag_creature->get_source_window();
	int							source_slot		= drag_creature->get_slot();


	// Drag onto self is no-op (~t_drag_creature will put the stack back in its original place later)
	if (source_slot == slot_number && source == this)
    {
		return;
    }

	// transferring creatures between windows costs movement.  Check if we have enough.
	int					    cost_in			= 0;
	int					    cost_out			= 0;
	t_creature_stack const& dest_stack   = (*m_army)[slot_number];
	t_creature_stack_ptr	source_stack		= drag_creature->get_data();
	t_creature_array const*	source_army = source->get_army();


	// compute cost or credit of a move.  Normally zero, unless moving from
	// one window to another.
	if (source != this && !source_stack->is_dead())
	{
		if (drag_creature->get_transfer_charge() > 0)
			cost_in = -drag_creature->get_transfer_charge(); // refund cost of moving into source
		else
			cost_in = get_transfer_cost( *source_stack, *source_army );
		if (m_slot[slot_number].transfer_charge > 0 )
			cost_out = -m_slot[slot_number].transfer_charge; // refund cost of moving into this window
		else
			cost_out = source->get_transfer_cost( dest_stack, *m_army );
	}

	// perform split dialog
	if ( drag_creature->use_split_dialog() && dest_stack.can_add( *source_stack ))
	{
		if ( !split_dialog( drag_creature, slot_number, cost_in, cost_out ) )
		{
			set_highlight( slot_number, false );
			source->select_creature( source_slot );
			return;
		}
	}
	else
	{
		// check if incoming creature has enough movement to enter this window
		// NOTE: We don't check this in is_valid_drag_drop, because this case
		// is not obvious to player and we want to have a special message displayed
		// if they try it
		if (!check_transfer_cost( *source_stack, slot_number, cost_in ))
			return;

		// check if armies can be merged.
		if (dest_stack.can_add( *source_stack ))
		{
			add( source_stack, slot_number, cost_in, drag_creature->get_transfer_charge() );
			drag_creature->accepted( this );
		}
		else
		{   // attempt to swap, instead
			if (!check_transfer_cost( dest_stack, slot_number, cost_out ))
				return;

			// expend movement
			int transfer_charge = m_slot[slot_number].transfer_charge;

			// swap source (dragged creature) with destination slot
			source_stack->change_raw_adventure_movement( -cost_in );
			m_army->swap( source_stack, slot_number );

			// take destination creature, expend movement
			source_stack->change_raw_adventure_movement( -cost_out );
			if (cost_in == 0)
				m_slot[slot_number].transfer_charge = drag_creature->get_transfer_charge();
			else if (cost_in > 0)
				m_slot[slot_number].transfer_charge = cost_in;
			else
				m_slot[slot_number].transfer_charge = 0;

			// swap destination creature with drag information
			if (cost_out == 0)
				drag_creature->swap( source_stack, transfer_charge );
			else if (cost_out > 0)
				drag_creature->swap( source_stack, cost_out );
			else
				drag_creature->swap( source_stack, 0 );
		}
	}
	set_highlight( slot_number, false );
	select_creature( slot_number );
	update();
	m_change_handler();
}

// ---------------------------------------------------------------------------
// creature array window
// attempt to merge another army into this one
// ---------------------------------------------------------------------------
void t_creature_array_window::merge( t_creature_array_window* source, bool move_creatures, bool move_live_heroes, bool move_dead_heroes )
{
	// try and add source to dest, preferably in the same slot.
	int               i;
	int               slot_number;
	t_creature_array& army = *source->m_army;
	int               cost;

	for (i = 0; i < k_size; i++)
	{
		if (army[i].get_number() == 0)
			continue;

		t_hero * hero = army[i].get_hero();

		if ( hero )
		{
			if ( hero->is_dead() )
			{
				if ( !move_dead_heroes )
					continue;
			}
			else
			{
				if ( !move_live_heroes )
					continue;
			}
		}
		else
		{
			if ( !move_creatures )
				continue;
		}

		// compute cost of merging this army.
		if (source->m_slot[i].transfer_charge > 0)
			cost = -source->m_slot[i].transfer_charge;
		else
			cost = get_transfer_cost( army[i], army );
		// don't merge if army can't spend the movement.
		if (army[i].get_raw_adventure_movement_left() < cost)
			continue;
		// find an open slot.
		if ((*m_army)[i].can_add( army[i] ))
			slot_number = i;
		else
		{
			for (slot_number = 0; slot_number < k_size; slot_number++)
				if ((*m_army)[slot_number].can_add( army[i] ))
					break;
			if (slot_number == k_size)
				continue;
		}
		// perform the merge.
		t_creature_stack_ptr stack = new t_creature_stack;

		army.swap( stack, i );
		add( stack, slot_number, cost, cost > 0 );
	}
	// update both windows.
	source->update();
	update();
	select_first();
}

// ---------------------------------------------------------------------------
// get cost of transferring into a creature array
// ---------------------------------------------------------------------------
int t_creature_array_window::get_transfer_cost( t_creature_stack const& stack,
							 				    t_creature_array const& army ) const
{
	int cost = m_base_transfer_cost;

	if (m_army->is_boat() == army.is_boat())
		return cost;
	
	int all_movement = stack.get_raw_adventure_movement_left();

	if (cost < all_movement)
		cost = all_movement;
	return cost;
}


// ---------------------------------------------------------------------------
// creature array window
// attempt to merge another army into this one
// ---------------------------------------------------------------------------
void t_creature_array_window::swap( t_creature_array_window* source )
{
	// try and add source to dest, preferably in the same slot.
	int               i;
	t_creature_array& army = *source->m_army;
	t_creature_array& dst_army = *m_army;
	int               cost = 0;

	for (i = 0; i < k_size; i++)
	{
		if (army[i].get_number() == 0 && dst_army[i].get_number() == 0)
			continue;

		// compute cost of merging this army.
		if (source->m_slot[i].transfer_charge > 0)
			cost = -source->m_slot[i].transfer_charge;
		else
			cost = get_transfer_cost( army[i], army );
		// don't merge if army can't spend the movement.
		if (army[i].get_raw_adventure_movement_left() < cost)
			continue;

		// perform the swap
		t_creature_stack_ptr stack = &army[i];
		dst_army.swap( stack, i );
		army.swap( stack, i );

		if (cost > 0)
			m_slot[i].transfer_charge = cost;
		else
			m_slot[i].transfer_charge = 0;
		// expend movement and merge them.
		stack->change_raw_adventure_movement( -cost );
	}
	// update both windows.
	source->update();
	update();
	select_first();
}

// ---------------------------------------------------------------------------
// Link the selection state of two windows together. Once done, selecting
// an element in any one window deselects all the others
// ---------------------------------------------------------------------------
void t_creature_array_window::link_selection( t_creature_array_window * linked_window )
{
	m_linked_windows->merge_window_into_list( linked_window );
}


// ---------------------------------------------------------------------------
// bar graph for creature array window
// ---------------------------------------------------------------------------
void t_creature_array_window::t_bar_graph::create( t_bitmap_layer const* bar,
												   t_bitmap_layer const* border,
												   t_screen_point const& offset,
												   t_window*             parent )
{
	m_bar = new t_bitmap_layer_window( bar, offset, parent );
	m_bar->set_visible( false );
	m_border = new t_bitmap_layer_window( border, offset, parent );
	m_border->set_visible( false );
}

// ---------------------------------------------------------------------------
// bar graph for creature array window
// ---------------------------------------------------------------------------
void t_creature_array_window::t_bar_graph::set( int value, int max_value )
{
	int           width;
	t_screen_rect rect;

	if (max_value < 1)
	{
		max_value = 1;
		value = 0;
	}
	if (value < 0)
		value = 0;
	if (value > max_value)
		value = max_value;
	width = m_bar->get_bitmap()->get_width();
	width = value * width / max_value;
	rect = m_bar->get_screen_rect();
	rect.right = rect.left + width;
	m_bar->move_screen_rect( rect );
}

// ---------------------------------------------------------------------------
// bar graph for creature array window
// ---------------------------------------------------------------------------
void t_creature_array_window::t_bar_graph::set_visible( bool arg )
{
	m_bar->set_visible( arg );
	m_border->set_visible( arg );
}

